package robocup.component.worldobjects;

import robocup.component.geometry.Vektor;

/**
 * Super-class for all movable objects, i.e. the player or a ball. Therefore it
 * extends the StaticObject by speed.
 */
public abstract class DynamicObject
    extends StaticObject {

  /**
   * The speed of the object.
   */
  protected final Vektor speed             = new Vektor();

  /**
   * The last seen position of the object.
   */
  protected final Vektor lastSeenPosition  = new Vektor();

  /**
   * The cycle, in which the object was seen last.
   */
  protected int    lastSeenBodyCycle = -1;

  /**
   * Is the position of the object reliable?
   */
  private boolean  isReliable        = true;

  /**
   * Is set to false, if the object is known to be non-existant. This may only
   * occur in training.
   */
  private boolean  exists            = true;

  // reliability of the object speed. true if speed calculation is
  // based on dist/dirchange, false if not.
  private boolean  speedReliable     = true;

  /**
   * A moveable abject
   * 
   * @param iCycle -
   *          time, when seen last (real cycle)
   * @param bodyCycle -
   *          time, when seen last (body-cycle)
   * @param iPosAbs -
   *          the absolute position
   * @param iSpeed -
   *          the speed
   */
  public DynamicObject(
      final int iCycle,
      final int bodyCycle,
      final Vektor iPosAbs,
      final Vektor iSpeed) {

    super(iCycle, bodyCycle, iPosAbs);
    this.speed.copy(iSpeed);
  }

  /**
   * Extrapolates dynamic Objects based on their speed.
   * 
   * @param n -
   *          horizont (cycles)
   * @param decay
   * 
   * @author rw
   */
  public void extrapolate(final int n,
      final double decay) {

    // as profiling shows that Math.pow is quiet slow
    // the common case of extrapoling by 1 cycle
    // is handled in a more efficient way
    if (n == 0) {
      return;
    }
    if (n == 1) {
      this.position.x += this.speed.x;
      this.position.y += this.speed.y;
      this.speed.x *= decay;
      this.speed.y *= decay;
    }
    else {
      this.position.x += (decay == 1) ? this.speed.x * n : this.speed.x * (1.0 - Math
          .pow(decay, n)) / (1.0 - decay);
      this.position.y += (decay == 1) ? this.speed.y * n : this.speed.y * (1.0 - Math
          .pow(decay, n)) / (1.0 - decay);
      this.speed.x *= Math.pow(decay, n);
      this.speed.y *= Math.pow(decay, n);
    }
    // no stamina update as the main actions are missing
  }

  /**
   * The objects position after a given number of cycles.
   * 
   * @param cycles -
   *          number of future cycles
   * @param decay -
   *          objects decay
   * @return position - the future position
   */
  public Vektor getFuturePos(final int cycles,
      final double decay) {

    final Vektor newPos = this.getPosition().cloned();
    final Vektor movement = this.speed.cloned();
    if (decay != 1.0) {
      movement.multThis((1.0 - Math.pow(decay, cycles)) / (1.0 - decay));
    }
    else {
      movement.multThis(cycles);
    }
    newPos.addToThis(movement);
    return newPos;
  }

  /**
   * The objects velocity after a given number of cycles.
   * 
   * @param cycles -
   *          number of future cycles
   * @param decay -
   *          objects decay
   * @return - the future velocity
   */
  public Vektor getFutureVelocity(final int cycles,
      final double decay) {

    final Vektor movement = this.speed.cloned();
    movement.multThis(Math.pow(decay, cycles));
    return movement;
  }

  /**
   * The final objects position if no acceleration occurs.
   * 
   * @param decay
   * @return The final objects position if no acceleration occurs.
   */
  public Vektor getFinalPosition(final double decay) {

    if (decay >= 1 || decay < 0) {
      throw new ArithmeticException(
          "Don't call 'DynamicObject.getFinalPosition(decay)' with decay >=1 or decay < 0!!");
    }
    return new Vektor(Vektor.XY,
        this.position.x + this.speed.x * 1 / (1 - decay),
        this.position.y + this.speed.y * 1 / (1 - decay));
  }

  /**
   * the age of an dynamic object. time in senseBody steps since the object was
   * last seen
   * 
   * @param currentBodySenseCycle
   * @return age
   */
  public int getAge(final int currentBodySenseCycle) {

    if (currentBodySenseCycle < this.getBodyCycle()) {
      System.out
          .println("DynamicObject.getAge called with wrong current Cycle. C-Object:" + this
              .getBodyCycle() + " C-Param:" + currentBodySenseCycle);
    }
    return currentBodySenseCycle - this.getBodyCycle();
  }

  /**
   * @param lastSeenPosition
   *          The lastSeenPosition to set.
   */
  public void setLastSeenPosition(final Vektor lastSeenPosition) {

    this.lastSeenPosition.x = lastSeenPosition.x;
    this.lastSeenPosition.y = lastSeenPosition.y;
  }

  /**
   * @return Returns the lastSeenPosition.
   */
  public Vektor getLastSeenPosition() {

    return this.lastSeenPosition;
  }

  /**
   * DynamicObject is reliable.
   * 
   * @return true if we know information about this object for sure
   */
  public boolean isReliable() {

    return this.isReliable;
  }

  /**
   * Set DynamicObject reliable (or not).
   * 
   * @param b
   */
  public void setReliable(final boolean b) {

    this.isReliable = b;
  }

  /**
   * @return Returns the object's velocity. Note that if you perform operations
   *         on this Vektor that change it, you need to clone it first!
   */
  public Vektor getSpeed() {

    return this.speed;
  }

  /**
   * @param speed
   *          The speed to set.
   */
  public void setSpeed(final Vektor speed) {

    this.speed.x = speed.x;
    this.speed.y = speed.y;
  }

  /**
   * @return Returns the speedReliable.
   */
  public final boolean speedIsReliable() {

    return this.speedReliable;
  }

  /**
   * @param speedReliable
   *          The speedReliable to set.
   */
  public final void setSpeedReliable(final boolean speedReliable) {

    this.speedReliable = speedReliable;
  }

  /**
   * @return Returns the lastSeenBodyCycle.
   */
  public int getLastSeenBodyCycle() {

    return this.lastSeenBodyCycle;
  }

  /**
   * @param lastSeenBodyCycle
   *          The lastSeenBodyCycle to set.
   */
  public void setLastSeenBodyCycle(final int lastSeenBodyCycle) {

    this.lastSeenBodyCycle = lastSeenBodyCycle;
  }

  /**
   * This method checks, if an object exists.
   * 
   * @return true, if existant
   */
  public boolean exists() {

    return this.exists;
  }

  /**
   * This method sets an object non-existant. Note that the default is true. The
   * bal exists always, only players should be set to non- existant. Beware of
   * the usage. Up to now, this was only used in training.
   * 
   * @param exists -
   *          false, if player is not existant
   */
  public void setExists(final boolean exists) {

    // set the position also away
    this.exists = exists;
    this.position.x -= 100;
  }
}
